﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace WpfCommander
{
    /// <summary> 
    /// Interaction logic for View.xaml
    /// </summary>
    public partial class View : UserControl
    {
        private bool beginUpdate;
        private int displayRowCount;
        private int selectedIndex;
        private int virtualListSize;
        private Size planeHeaderSize = new Size(0, 20);


        public View()
        {
            this.Items = new List<ViewItem>(30);

            InitializeComponent();
        }


        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int SelectedIndex
        {
            get
            {
                return selectedIndex;
            }
            set
            {
                if (selectedIndex != value)
                {
                    selectedIndex = value;
                    if (!RowIsVisible(selectedIndex))
                    {
                        ScrollToRow(selectedIndex);
                    }

                    UpdateSelection();
                    OnItemSelected(new ItemSelectedEventArgs(selectedIndex, GetItem(selectedIndex)));
                }
                else if (!RowIsVisible(selectedIndex))
                {
                    ScrollToRow(selectedIndex);
                }
            }
        }

        [Category("Layout")]
        public int VirtualListSize
        {
            get { return virtualListSize; }
            set
            {
                virtualListSize = value;
                selectedIndex = 0;
                displayRowCount = 0;

                verticalScrollBar.Value = 0;                
                verticalScrollBar.Maximum = virtualListSize;
                verticalScrollBar.SmallChange = 1;
                verticalScrollBar.LargeChange = 3;                
                //verticalScrollBar.UpdateLayout();
                UpdateBricks();
                //OnLayout(new LayoutEventArgs(this, "PlaneBlockCount"));
            }
        }

        [DefaultValue(10)]
        [Category("Layout")]
        public int Spacing { get; set; }

        [Category("Layout")]
        public double RowHeight { get; set; }

        protected List<ViewItem> Items { get; set; }


        public event ItemSelectedEventHandler ItemSelected;


        protected virtual void Fill(int count)
        {
            // Add or delete bricks to fill all client area
            if (count < this.Items.Count)
            {
                for (int i = this.Items.Count - 1; i >= count; i--)
                {
                    this.Items[i].Visibility = Visibility.Hidden;
                    grid.Children.Remove(this.Items[i]);
                    this.Items.RemoveAt(i);
                }
            }

            if (count > this.Items.Count)
            {
                int addCount = count - this.Items.Count;
                AddControls(addCount);
            }
        }

        protected virtual void AddControls(int count)
        {
            for (int i = 0; i < count; i++)
            {
                ViewItem control = CreateItem();
                control.Selected += ViewItem_Selected;
                control.HorizontalAlignment = HorizontalAlignment.Left;
                control.VerticalAlignment = VerticalAlignment.Top;
                control.Width = 200;
                control.Height = this.RowHeight;
                control.Margin = new Thickness(50, 50, 0, 0);
                grid.Children.Add(control);

                this.Items.Add(control);
            }
        }

        protected virtual ViewItem CreateItem()
        {
            var result = new ViewItem();
            result.PreviewKeyDown += UserControl_PreviewKeyDown;
            return result;
        }

        protected virtual void RetriveItem(ViewItem control, int realIndex)
        {
            ViewItem item = (ViewItem)control;

            if (item.Index != realIndex || string.IsNullOrEmpty(item.Text))
            {
                item.Text = realIndex.ToString();
                item.Index = realIndex;
            }
        }

        protected virtual void SetSelected(ViewItem item, int index, bool isSelected)
        {
            item.IsSelected = isSelected;
        }

        protected virtual void UpdateBricks()
        {
            if (this.VirtualListSize == 0 || RenderSize.Width == 0 || RenderSize.Height == 0)
            {
                planeHeaderSize.Width = 0;
                return;
            }

            Size itemSize = new Size(RenderSize.Width - (verticalScrollBar.Visibility == Visibility.Visible ? verticalScrollBar.Width : 0), this.RowHeight + Spacing);
            Size displaySize = new Size(RenderSize.Width - Spacing - (verticalScrollBar.Visibility == Visibility.Visible ? verticalScrollBar.Width : 0), RenderSize.Height - Spacing - planeHeaderSize.Height);

            // Сancel if the size of window didn't change enough
            int newRowCount = (int)(displaySize.Height / itemSize.Height);
            if ((displayRowCount == newRowCount) || newRowCount == 0)
            {
                return;
            }

            int startRow = (int)verticalScrollBar.Value;
            if (startRow > 0 && startRow + displayRowCount >= this.VirtualListSize) // Is end?
            {
                startRow = Math.Max(0, GetMaxRow() + 1 - newRowCount);
            }

            this.beginUpdate = true;

            Fill(newRowCount);


            // Set new columns and rows numbers
            this.displayRowCount = newRowCount;

            int startIndex = GetStartIndex(startRow);
            int maxRow = GetMaxRow();
            int endRow = startRow + Math.Min(displayRowCount, maxRow - startRow + 1) - 1;

            // Full rows
            int y = (int)planeHeaderSize.Height + Spacing;
            int itemIndex = 0;
            for (int row = startRow, i = startIndex; row <= endRow; row++, y += (int)itemSize.Height, itemIndex++, i++)
            {
                ViewItem item = this.Items[itemIndex];
                RetriveItem(item, i);
                SetSelected(item, itemIndex, selectedIndex == i);
                item.Margin = new Thickness(0, y, 0, 0);
                item.Width = itemSize.Width;
                item.Visibility = Visibility.Visible;
            }

            // Other
            for (; itemIndex < this.Items.Count; y += (int)itemSize.Height, itemIndex++)
            {
                ViewItem item = this.Items[itemIndex];
                item.Margin = new Thickness(0, y, 0, 0);
                item.Width = itemSize.Width;
                item.Visibility = Visibility.Hidden;
            }


            // Update Scroll settings
            verticalScrollBar.ViewportSize = displayRowCount;
            verticalScrollBar.LargeChange = displayRowCount;
            verticalScrollBar.Maximum = this.VirtualListSize - displayRowCount;
            
            SetScroll(startIndex);
            verticalScrollBar.Visibility = (GetMaxScrollRow() > 0 ? Visibility.Visible : Visibility.Hidden);
            planeHeaderSize.Width = RenderSize.Width;


            this.beginUpdate = false;
        }

        protected ViewItem GetItem(int index)
        {
            int displayIndex = ToDisplayIndex(index);
            return this.Items[displayIndex];
        }

        protected void ScrollToContol(int index)
        {
            SetScroll(index);
        }

        protected void ScrollToRow(int startRow)
        {
            if (!beginUpdate && this.Items.Count > 0)
            {
                //SuspendLayout();

                startRow = Math.Min(GetMaxScrollRow(), startRow);

                int maxRow = GetMaxRow();
                int endRow = startRow + Math.Min(displayRowCount, maxRow - startRow + 1) - 1;

                int selectedIndex = this.SelectedIndex;

                // Full rows
                int itemIndex = 0;
                for (int row = startRow, i = startRow; row <= endRow; row++, itemIndex++, i++)
                {
                    ViewItem item = this.Items[itemIndex];
                    RetriveItem(item, i);
                    SetSelected(item, itemIndex, selectedIndex == i);
                    item.Visibility = Visibility.Visible;
                }

                // Other
                for (; itemIndex < this.Items.Count; itemIndex++)
                {
                    ViewItem item = this.Items[itemIndex];
                    item.Index = -1;
                    item.Visibility = Visibility.Hidden;
                }

                SetScroll(startRow);
                //UpdateBorder();
                //ResumeLayout(false);
            }
        }

        protected virtual void OnItemSelected(ItemSelectedEventArgs e)
        {
            if (ItemSelected != null)
            {
                ItemSelected(this, e);
            }
        }


        private int GetMaxRow()
        {
            return Math.Max(0, this.VirtualListSize - 1);
        }

        private int GetMaxScrollRow()
        {
            return Math.Max(0, GetMaxRow() - displayRowCount + 1);
        }

        private int GetStartIndex()
        {
            return GetStartIndex((int)verticalScrollBar.Value);
        }

        private int GetStartIndex(int row)
        {
            return Math.Min(row, this.VirtualListSize - 1);
        }

        private bool RowIsVisible(int row)
        {
            return (row >= verticalScrollBar.Value && row < verticalScrollBar.Value + displayRowCount);
        }

        private int ToDisplayIndex(int index)
        {
            int offset = GetStartIndex();
            if (index >= offset)
            {
                return index - offset;
            }

            throw new ArgumentOutOfRangeException();
        }

        private int ToIndex(int displayIndex)
        {
            int offset = GetStartIndex();

            return offset + displayIndex;
        }

        private void SetScroll(int value)
        {
            int newValue = MathHelper.Range((int)verticalScrollBar.Minimum, value, GetMaxScrollRow());;
            if (newValue != (int)verticalScrollBar.Value)
            {
                verticalScrollBar.Value = newValue;
            }
        }

        private void UpdateSelection()
        {
            for (int i = 0; i < this.Items.Count; i++)
            {
                ViewItem item = this.Items[i];

                SetSelected(item, i, (item.Index == selectedIndex));
            }
        }

        private void MoveSelection(int value)
        {
            int row = MathHelper.Range(0, this.SelectedIndex + value, this.VirtualListSize - 1);
            if ((value == 1 || value == -1) && !RowIsVisible(row))
            {
                ScrollToRow(MathHelper.Range(0, (int)this.verticalScrollBar.Value + value, this.VirtualListSize - 1));
            }
            this.SelectedIndex = row;
        }

        private void verticalScrollBar_ValueChanged(object sender, EventArgs e)
        {
            ScrollToRow((int)verticalScrollBar.Value);
        }

        private void ViewItem_Selected(object sender, EventArgs e)
        {
            if (!beginUpdate)
            {
                this.SelectedIndex = ((ViewItem)sender).Index;
            }
        }

        private void UserControl_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            SetScroll((int)verticalScrollBar.Value - e.Delta / 120);
        }

        private void UserControl_LayoutUpdated(object sender, EventArgs e)
        {
            UpdateBricks();
        }

        public void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (this.VirtualListSize == 0 || displayRowCount == 0)
            {
                return;
            }

            switch (e.Key)
            {
                case Key.Up:
                    {
                        MoveSelection(-1);
                        break;
                    }
                case Key.Down:
                    {
                        MoveSelection(1);
                        break;
                    }
                case Key.PageUp:
                    {
                        MoveSelection(-displayRowCount);
                        break;
                    }
                case Key.PageDown:
                    {
                        MoveSelection(displayRowCount);
                        break;
                    }
                case Key.Home:
                    {
                        this.SelectedIndex = 0;
                        break;
                    }
                case Key.End:
                    {
                        this.SelectedIndex = this.VirtualListSize - 1;
                        break;
                    }
            }
        }
    }

    public delegate void ItemSelectedEventHandler(object sender, ItemSelectedEventArgs args);

    public class ItemSelectedEventArgs : EventArgs
    {
        public ItemSelectedEventArgs(int index, ViewItem item)
        {
            this.Index = index;
            this.Item = item;
        }


        public int Index { get; private set; }

        public ViewItem Item { get; private set; }
    }

    public static class MathHelper
    {
        public static int Range(int minValue, int value, int maxValue)
        {
            return Math.Min(Math.Max(minValue, value), maxValue);
        }
    }
}
